#pragma once
#include<iostream>
#include<vector>
#include<string>
#include<unordered_map>
#include<fstream>
#include"util.hpp"
#include<mutex>
#include"log.hpp"
namespace ns_index
{
//正排索引，通过文档id找到文档的内容
 struct DocInfo
 {
   std::string doc_title;
   std::string doc_content;
   std::string doc_url;
   std::uint64_t doc_id;
 };

 //倒排索引，通过关键字找到文档id
 struct InvertedElem
 {
   uint64_t doc_id;
   std::string word;
   int weight;//权重
 };

  typedef std::vector<InvertedElem> InvertedList;//倒排拉链

  class Index 
  {
    private:
      //正排索引的数据结构用数组
      std::vector<DocInfo> forward_index;
      //倒排索引一定是一个关键字和一组(个)InvertedElem相对应,
      //但是这里用unordered_map的原因是什么
      std::unordered_map<std::string,InvertedList> invert_index;
     
      //构建单例要把构造函数放到private中，这样就不能通过构造创建对象了
      //单例模式：一个类只有一个对象；所以我们也不想让其有赋值以及拷贝构造
      Index(){};
      Index(const Index&)=delete;
      Index& operator=(const Index&)=delete;

      static Index* instance; //要在类外面初始化，一般设为nullptr。
      static std::mutex mtx;
    public:
      static Index* GetInstance()
      {
        if(nullptr==instance)//因为单例模式只创建一次对象，所以再多线程的情况下
        {                    //如果已经创建完了，各个线程就不需要在去竞争锁了
          mtx.lock();//因为第一次创建对象的时候，是存在很大的线程安全的问题，可能一次要创建两个对象
          if(nullptr==instance)   //所以要加锁
          {
            instance =new Index();
          }
          mtx.unlock();
        }
        return instance;
      }
         
      ~Index();
    public:
      //正排索引：根据id找到文档
      DocInfo* GetForwardIndex(uint64_t doc_id)
      {
        if(doc_id>=forward_index.size())
        {
          std::cerr<<"doc_id out range,error"<<std::endl;
          return nullptr;
        }
        return &forward_index[doc_id] ;
      }
      //倒排索引：根据关键字获得倒排拉链
      InvertedList* GetInvertedList(const std::string& word)
      {
        auto iter=invert_index.find(word);
        if(iter==invert_index.end())
        {
            std::cerr<<word<<" have no InvertedList"<<std::endl;
            return nullptr;
        }

        return &(iter->second);
      }
      //根据去标签，格式化之后的文档，构建正排和倒排索引
      bool BuildIndex(const std::string &input)//parser处理完的数据
      {
   //   std::ifstream in(input,std::ios::in | std::ios::binary);
    std::ifstream in(input,std::ios::in|std::ios::binary); 
      if(!in.is_open())
      {
        std::cerr<<"sorry "<<input<<"open error"<<std::endl; 
        return false;
      }
      int count=0;
      std::string line;
      
      while(std::getline(in,line))
      {
        DocInfo* doc=BuildForwardIndex(line);
        if(doc==nullptr)
        {
         std::cerr<<"build "<<line<<" error"<<std::endl;
          continue;
        }
        BuildInvertedIndex(*doc);
        ++count;
        if(count%100==0)
        {
       //   std::cout<<"当前已经建立的索引文档 "<<count<<std::endl;
          LOG(RORMAL,"当前已经建立的索引文档"+std::to_string(count));
        }
      }

      in.close();
        return true;
     }
    private:

      DocInfo* BuildForwardIndex(const std::string& line)
      {
            //1.解析line，字符串的切分存放到一个vector中
        std::vector<std::string> results;
        std::string sep="\3";
        ns_util::StringUtil::Split(line,&results,sep);
        if(results.size()!=3)
        {
          std::cout<<results.size()<<std::endl;
          return nullptr;
        }
            //2.将DocInfo被vector填充
        DocInfo doc;
        doc.doc_title=results[0];
        doc.doc_content=results[1];
        doc.doc_url=results[2];
        doc.doc_id=forward_index.size();
      
            //3.将doc插入到forward_index中
          forward_index.push_back(std::move(doc));//move减少拷贝提高效率
          return &forward_index.back();
      }

      bool BuildInvertedIndex(const DocInfo& doc)
      {
        struct word_cnt
        {
          int title_cnt;
          int content_cnt;
      word_cnt():title_cnt(0),content_cnt(0){};
        };

        //保存词频的映射表
        std::unordered_map<std::string,word_cnt> word_map;
        //统计标题的词频
        std::vector<std::string> title_words;
        ns_util::JiebaUtil::CutString(doc.doc_title,&title_words);
        for(auto& s:title_words)
        {  
          boost::to_lower(s);//将我们的分词统一转换成小写的，因为搜索的时候不区分大小写
           word_map[s].title_cnt++;
        }
        //for debug
//       if(doc.doc_id==7470)
//        {
//          for(auto& s:title_words)
//            std::cout<<"title: "<<s<<std::endl;
//        }
        //统计内容的词频
        std::vector<std::string> content_words ;
        ns_util::JiebaUtil::CutString(doc.doc_content,&content_words );
        for(auto& s:content_words)
        {
          boost::to_lower(s);
          word_map[s].content_cnt++;
        }

//        if(doc.doc_id==7470)
//        {
//          for(auto& s:content_words)
//          {
//            std::cout<<"content: "<<s<<std::endl;
//          }
//        }
        
#define X 10
#define Y 1
        for(auto& word_pair:word_map)
        {
          InvertedElem item;
          item.doc_id=doc.doc_id;
          item.word=word_pair.first;
          item.weight=X*word_pair.second.title_cnt+Y*word_pair.second.content_cnt ;
          InvertedList& inverted_list=invert_index[word_pair.first];//map[]返回的是value的引用
          inverted_list.push_back(std::move(item));
        }
          return true;
      }
  };
 Index* Index::instance=nullptr;
 std::mutex Index::mtx;
}
